from django.shortcuts import render
from django.views import View
from meiduo_mall.utils.views import LoginRequiredMixinJSONMixin
from django_redis import get_redis_connection
from apps.goods.models import SKU
from decimal import Decimal
from django import http
from apps.users.models import Address
from apps.orders.models import OrderGoods, OrderInfo
from django.db import transaction
from django.utils import timezone
import json

# Create your views here.


class OrderSettlementView(LoginRequiredMixinJSONMixin, View):
    def get(self, request):
        """结算订单逻辑实现"""
        # 查询当前登录用户未被逻辑删除的地址
        address_model_list = request.user.addresses.filter(is_deleted=False)

        # 查询redis购物车中未被勾选的商品信息
        user_id = request.user.id
        redis_conn = get_redis_connection('carts')
        redis_cart = redis_conn.hgetall('carts_%s' % user_id)
        redis_selected = redis_conn.smembers("selected_%s" % user_id)

        new_cart = {}
        for sku_id in redis_selected:
            new_cart[int(sku_id)] = int(redis_cart[sku_id])

        # 将地址模型列表转字典列表
        addresses = []
        for address in address_model_list:
            addresses.append({
                "id": address.id,
                "province": address.province.name,
                'city': address.city.name,
                "district": address.district.name,
                "place": address.place,
                "receiver": address.receiver,
                "mobile": address.mobile,
            })

        # 将购物车商品列表转字典列表
        sku_ids = new_cart.keys()
        sku_model_list = SKU.objects.filter(id__in=sku_ids)
        skus = []
        for sku in sku_model_list:
            skus.append({
                'id': sku.id,
                'name': sku.name,
                'default_image_url': sku.default_image.url,
                'count': new_cart[sku.id],
                "price": sku.price,
            })

        # 指定邮费
        # 邮费是金钱，对于钱的精度必须非常高的，所以金钱的定义不能直接使用简单的float
        # python提供了定义金钱的类型 Decimal
        freight = Decimal(11.00)

        # 构造响应数据
        context = {
            'addresses': addresses,
            'skus': skus,
            'freight': freight,
        }

        # 响应结果
        return http.JsonResponse({"code": 0, 'errmsg': 'OK', 'context': context})


class OrderCommitView(LoginRequiredMixinJSONMixin, View):
    """订单提交"""
    def post(self, request):
        """保存订单信息和商品信息"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        address_id = json_dict.get('address_id')
        pay_method = json_dict.get('pay_method')

        # 校验参数
        if not all([address_id, pay_method]):
            return http.JsonResponse({"code": 400, 'errmsg': '缺少必传参数'})
        try:
            address = Address.objects.get(id=address_id)
        except Address.DoesNotExist:
            return http.JsonResponse({'code': 400, 'errmsg': '参数address_id错误'})
        # 判断支付方式是否在固定范围内(由商城限制和支持的)
        if pay_method not in [OrderInfo.PAY_METHODS_ENUM['CASH'], OrderInfo.PAY_METHODS_ENUM['ALIPAY']]:
            return http.JsonResponse({'code': 400, 'errmsg': '参数pay_method错误'})

        # 在操作订单相关的数据表时,显式的开启一次事务,使用上下文
        with transaction.atomic():
            # 操作数据之前,创建事务保存点,用来记录数据操作前的初始状态,方便回滚还原事务
            save_id = transaction.savepoint()

            try:
                # 生成订单号,order_id = '年月日是分秒'+'9位用户id'
                # strftime('时间格式'):时间对象转时间字符串.时间格式<====>%Y%m%d%H%M%S
                order_id = timezone.localtime().strftime('%Y%m%d%H%M%S') + ('%09d' % request.user.id)
                # 保存订单基本信息 OrderInfo（一）
                order = OrderInfo.objects.create(
                    order_id = order_id,
                    user = request.user,
                    address = address,
                    total_count = 0,
                    total_amount = Decimal(0.00),
                    freight = Decimal(11.00),
                    pay_method = pay_method,
                    # 订单状态和支付方式绑定:支付宝<-->待支付;货到付款<-->待发货
                    status = OrderInfo.ORDER_STATUS_ENUM['UNPAID'] if pay_method==OrderInfo.PAY_METHODS_ENUM['ALIPAY'] else OrderInfo.ORDER_STATUS_ENUM['UNSEND']
                )

                # 从redis读取购物车中被勾选的商品信息
                user_id = request.user.id
                redis_conn =  get_redis_connection('carts')
                redis_cart = redis_conn.hgetall('carts_%s' % user_id)
                redis_selected = redis_conn.smembers('selected_%s' % user_id)
                new_cart = {}
                for sku_id in redis_selected:
                    new_cart[int(sku_id)] = int(redis_cart[sku_id])

                # 遍历购物车中被勾选的商品信息
                sku_ids = new_cart.keys()
                # 提交订单时,查询商品绝不能使用filter(id__in=sku_ids)
                # 因为filter返回查询集,而查询集有缓存;但是,提交订单数据时是实时更新库存和销量的,此时库存和销量不能为缓存数据
                for sku_id in sku_ids:
                    # 注意:下单操作是在一个死循环中进行的
                    while True:
                        # 查询SKU信息
                        sku = SKU.objects.get(id=sku_id)

                        # 获取原始库存和销量:作为乐观锁标记,标记数据操作前的初始状态
                        origin_stock = sku.stock
                        origin_sales = sku.sales

                        # 判断SKU库存:是否满足购买量
                        sku_count = new_cart[sku_id]
                        # 购买量大于库存量-->回滚事务还原数据并返回库存不足
                        if sku_count > origin_stock:
                            # 回滚事务,还原数据
                            transaction.savepoint_rollback(save_id)
                            return http.JsonResponse({"code": 400, 'errsmg': '库存不足'})

                        # 模拟并发下单的错误,放大网络延迟,
                        # import time
                        # time.sleep(15)

                        # 购买量小于库存量
                        # SKU减少库存，增加销量
                        # sku.stock -= sku_count # sku.stock = sku.stock - sku_count
                        # sku.sales += sku_count # sku.sales = sku.sales + sku_count
                        # sku.save()

                        # 计算新库存和新销量
                        new_stock = origin_stock - sku_count
                        new_sales = origin_sales + sku_count
                        # 使用乐观锁操作SKU减少库存,增加销量
                        # update()返回值是影响行数.若发现资源竞争,则不执行update
                        result = SKU.objects.filter(id=sku_id, stock=origin_stock).update(stock=new_stock, sales=new_sales)
                        if result == 0:
                            # 说明有资源竞争,重新下单,直到库存不足
                            # 注意:下单操作是在一个死循环中进行的
                            continue

                        # 修改SPU销量
                        sku.spu.sales += sku_count
                        sku.spu.save()

                        # 保存订单商品信息 OrderGoods（多）
                        OrderGoods.objects.create(
                            order = order,
                            sku = sku,
                            count = sku_count,
                            price = sku.price,
                        )

                        # 保存商品订单中总价和总数量
                        order.total_count += sku_count    # 总数量
                        order.total_amount += sku_count * sku.price   # 总金额

                        # 购买成功,跳出死循环
                        break

                # 添加邮费和保存订单信息:邮费只累计一次,要放在循环外
                order.total_amount += order.freight     # 实付款
                order.save()
            except BaseException:
                transaction.savepoint_rollback(save_id)
                return http.JsonResponse({"code": 400, 'errmsg': '下单失败'})

            # 订单相关数据表操作都成功,则显式提交一次事务(同步数据到Mysql数据库)
            transaction.savepoint_commit(save_id)

        # 清除购物车中已结算的商品
        # 响应提交订单结果
        return http.JsonResponse({'code': 0, 'errmsg': 'OK', 'order_id': order_id})